﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace SocialLearning
{
    public class PingListener
    {
        #region Constants

        const int DefaultPort = 1337;

        #endregion

        #region Fields

        private Thread thread;
        private Socket socket;
        private bool done;
        private EndPoint address;
        private int port;

        public event PingHandler PingReceived;

        #endregion

        #region Properties

        public int Port { get { return (port); } }

        #endregion

        #region Constructor

        public PingListener(IPAddress address, int port)
        {
            this.address = new IPEndPoint(address, port);
            this.port = port;
        }

        public PingListener(int port) : this(IPAddress.Any, port) { }

        public PingListener() : this(IPAddress.Any, DefaultPort) { }

        #endregion

        #region Public Methods

        public void StartListen()
        {
            lock (address) { if (thread != null) { return; } }
            StopListen();
            done = false;
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            socket.Bind(address);
            thread = new Thread(new ThreadStart(ListenForPing));
            thread.Start();
        }

        public void StopListen()
        {
            lock (address) { if (thread == null) { return; } }
            done = true;
            socket.Close();
            thread.Join();
            thread = null;
        }

        #endregion

        #region Dispatcher Thread

        private void ListenForPing()
        {
            byte[] data = new byte[512];
            while (true)
            {
                lock (address) { if (done) { break; } }
                try
                {
                    int size = socket.Receive(data);
                    int type; string str;
                    try
                    {
                        if (ParsePacket(data, size, out type, out str) == false) { continue; }
                        OnPing(type, str);
                    }
                    catch { }
                }
                catch { return; }
            }
        }

        #endregion

        #region Private Methods

        private bool ParsePacket(byte[] data, int size, out int type, out string str)
        {
            str = null; if (size < 4) { type = 0; return (false); }
            type = ((int)data[0] << 24) | ((int)data[1] << 16) |
                   ((int)data[2] << 8) | ((int)data[3]);
            int i = size - 4;
            if (i > 0)
            {
                char[] a = new char[i];
                for (int j = 0; j < i; j++) { a[j] = (char)data[j + 4]; }
                str = new string(a);
            }
            return (true);
        }

        #endregion

        #region Event Invoker

        protected virtual void OnPing(int type, string data)
        {
            PingHandler received = PingReceived;
            if (received == null) { return; }
            received.BeginInvoke(type, data, null, null);
        }

        #endregion
    }

    public delegate void PingHandler(int pingType, string pingData);
}
